﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swcommands;
using SolidWorks.Interop.swconst;
using SolidWorks.Interop.swpublished;

using SwAddinHelpers.Contracts;

using System.Reflection;
using SwAddinHelpersComExtender;

namespace SwAddinHelpers.Sinks
{
    public enum enumEventSink
    {
        // sw app events
        SW_DestroyNotify,
        SW_ActiveDocChangeNotify,
        SW_ActiveModelDocChangeNotify,
        // custom events
        Custom_ActiveModelDocChangePreNotify,
        // document events
        Document_FileSaveNotify,
        Document_DestroyNotify,
        Document_NewSelectionNotify
    }

    public class SolidWorksEventArgs : EventArgs
    {
        public SolidWorksEventArgs(
            string Filename = "",
            swDestroyNotifyType_e DestroyType = swDestroyNotifyType_e.swDestroyNotifyDestroy,
            string activeDocChangePreNotify_PathOrTitle = "",
            string activeDocChangePreNotify_CreateDate = "")
        {
            filename = Filename;
            destroyType = DestroyType;
            ActiveDocChangePreNotify_PathOrTitle = activeDocChangePreNotify_PathOrTitle;
            ActiveDocChangePreNotify_CreateDate = activeDocChangePreNotify_CreateDate;
        }

        public string filename;
        public swDestroyNotifyType_e destroyType;
        public string ActiveDocChangePreNotify_PathOrTitle;
        public string ActiveDocChangePreNotify_CreateDate;
    }

    public sealed class swAppEventSink: AddinProxy 
    {
        bool flag642010 = false;
        
        private const string NO_DOC = "?NO_LAST_DOCUMENT";
        private string LastKnownDocumentPath = NO_DOC;
        private string LastKnownDocumentCreate = NO_DOC;

        public swAppEventSink(ISwAddinBase owner)
        {
            _OwnerAddin = owner;
        }

        public event EventHandler SW_ActiveDocChangeNotify;
        public event EventHandler SW_ActiveModelDocChangeNotify;
        public event EventHandler SW_DestroyNotify; // this will never fire since the addin is disconnected before SW exits

        public delegate void SolidWorks_Generic_Delegate(object sender, SolidWorksEventArgs e);
        public event SolidWorks_Generic_Delegate Document_FileSaveNotify;
        public event SolidWorks_Generic_Delegate Document_DestroyNotify;
        public event SolidWorks_Generic_Delegate Document_NewSelectionNotify;

        public event SolidWorks_Generic_Delegate Custom_ActiveModelDocChangePreNotify;

        public Delegate GetDelegate(enumEventSink sinkType)
        {
            Delegate d = null;

            EventInfo eventInfo = this.GetType().GetEvent(sinkType.ToString());

            if (eventInfo != null)
            {
                try
                {
                    d = (Delegate)this.GetType().GetField(
                        eventInfo.Name,
                        System.Reflection.BindingFlags.Instance |
                        System.Reflection.BindingFlags.NonPublic).GetValue(this);
                }
                catch { }
            }

            return d;
        }

        public Dictionary<string, Delegate> GetDelegates()
        {
            Dictionary<string, Delegate> dList = new Dictionary<string, Delegate>();

            EventInfo[] events = this.GetType().GetEvents();

            foreach (EventInfo e in events)
            {
                try
                {
                    Delegate d = (Delegate)this.GetType().GetField(
                        e.Name,
                        System.Reflection.BindingFlags.Instance |
                        System.Reflection.BindingFlags.NonPublic).GetValue(this);

                    dList.Add(e.Name, d);
                }
                catch { }
                
                /*Delegate[] delegates = eventDelagate.GetInvocationList();

                foreach (Delegate dlg in delegates)
                {
                    dlg.Method.Invoke(dlg.Target, new object[] { this, e });
                }*/

            }

            return dList;
        }

        public string[] GetInvocationList(Delegate d)
        {
            if (d == null) return new string[] { "\r\n\tno invocations" };
            if (!_OwnerAddin.Debug.IsDebuggerAttached) return new string[] { "\r\n\tdebugger not attached" };

            string[] returnList = new string[1];
            int count = 0;
            foreach (Delegate d1 in d.GetInvocationList())
            {
                returnList[count] = "\r\n\t" + d1.Target.ToString() + "." + d1.Method.ToString();

                Array.Resize<string>(ref returnList, returnList.Length + 1);
                count ++;
            }

            return returnList;
        }

        public void Untie()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.SystemExecution);
            //*_OwnerAddin.SolidWorksApplication.CommandCloseNotify -= new DSldWorksEvents_CommandCloseNotifyEventHandler(SolidWorksApplication_CommandCloseNotify);
            _OwnerAddin.SolidWorksApplication.ActiveDocChangeNotify -= new DSldWorksEvents_ActiveDocChangeNotifyEventHandler(SolidWorksApplication_ActiveDocChangeNotify);
            _OwnerAddin.SolidWorksApplication.ActiveModelDocChangeNotify -= new DSldWorksEvents_ActiveModelDocChangeNotifyEventHandler(SolidWorksApplication_ActiveModelDocChangeNotify);
            _OwnerAddin.SolidWorksApplication.DestroyNotify -= new DSldWorksEvents_DestroyNotifyEventHandler(SolidWorksApplication_DestroyNotify);

            _OwnerAddin.SolidWorksApplication.FileOpenPreNotify -= new DSldWorksEvents_FileOpenPreNotifyEventHandler(SolidWorksApplication_FileOpenPreNotify);
            _OwnerAddin.SolidWorksApplication.FileOpenPostNotify -= new DSldWorksEvents_FileOpenPostNotifyEventHandler(SolidWorksApplication_FileOpenPostNotify);
            _OwnerAddin.SolidWorksApplication.FileNewPreNotify -= new DSldWorksEvents_FileNewPreNotifyEventHandler(SolidWorksApplication_FileNewPreNotify);
            _OwnerAddin.SolidWorksApplication.FileNewNotify2 -= new DSldWorksEvents_FileNewNotify2EventHandler(SolidWorksApplication_FileNewNotify2);
        }

        public void Tie()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.SystemExecution);
            //*_OwnerAddin.SolidWorksApplication.CommandCloseNotify += new DSldWorksEvents_CommandCloseNotifyEventHandler(SolidWorksApplication_CommandCloseNotify);
            _OwnerAddin.SolidWorksApplication.ActiveDocChangeNotify += new DSldWorksEvents_ActiveDocChangeNotifyEventHandler(SolidWorksApplication_ActiveDocChangeNotify);
            _OwnerAddin.SolidWorksApplication.ActiveModelDocChangeNotify += new DSldWorksEvents_ActiveModelDocChangeNotifyEventHandler(SolidWorksApplication_ActiveModelDocChangeNotify);
            _OwnerAddin.SolidWorksApplication.DestroyNotify += new DSldWorksEvents_DestroyNotifyEventHandler(SolidWorksApplication_DestroyNotify);

            _OwnerAddin.SolidWorksApplication.FileOpenPreNotify += new DSldWorksEvents_FileOpenPreNotifyEventHandler(SolidWorksApplication_FileOpenPreNotify);
            _OwnerAddin.SolidWorksApplication.FileOpenPostNotify += new DSldWorksEvents_FileOpenPostNotifyEventHandler(SolidWorksApplication_FileOpenPostNotify);
            _OwnerAddin.SolidWorksApplication.FileNewPreNotify += new DSldWorksEvents_FileNewPreNotifyEventHandler(SolidWorksApplication_FileNewPreNotify);
            _OwnerAddin.SolidWorksApplication.FileNewNotify2 +=new DSldWorksEvents_FileNewNotify2EventHandler(SolidWorksApplication_FileNewNotify2);
        }

        #region SW - File New/Open Sinks
        
        int  SolidWorksApplication_FileNewNotify2(object newDoc, int DocType, string TemplateName)
        {
            return 0;
        }

        int SolidWorksApplication_FileNewPreNotify(int DocType, string TemplateName)
        {
            return 0;
        }

        int SolidWorksApplication_FileOpenPostNotify(string FileName)
        {
            return 0;
        }

        int SolidWorksApplication_FileOpenPreNotify(string FileName)
        {
            return 0;
        }

        #endregion

        #region SW Sink
        int SolidWorksApplication_DestroyNotify()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(SW_DestroyNotify)));
            
            if (SW_DestroyNotify != null) SW_DestroyNotify(null, new EventArgs());

            return 0;
        }

        int SolidWorksApplication_ActiveModelDocChangeNotify()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(SW_ActiveModelDocChangeNotify)));

            // Resubscribe doc events to current doc.
            if (!flag642010)
            {
                //flag642010 = true;
                //System.Windows.Forms.MessageBox.Show("whent TP is visible then the events don't get subscribed right. this is one time only.");
            }

            DetachLastDocumentSink();  
            AttachCurrentDocumentSink();
            
            if (SW_ActiveModelDocChangeNotify != null) 
                SW_ActiveModelDocChangeNotify(null, new EventArgs());

            return 0;
        }

        int SolidWorksApplication_ActiveDocChangeNotify()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(SW_ActiveDocChangeNotify)));

            if (SW_ActiveDocChangeNotify != null) SW_ActiveDocChangeNotify(null, new EventArgs());

            return 0;
        }
        #endregion

        #region Document Sink

        private Guid _DocumentSinkGuid = new Guid("83A33D32-37C5-11CE-BFD4-00400513BB57");
        private IConnectionPoint _DocumentSinkPoint;
        private int _DocumentSinkCookie;
        private DPartDocEventsImpl _DocumentSinkImpl;

        void Custom_PreNotifyCallback(string a, string b)
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(Custom_ActiveModelDocChangePreNotify)));
            if (Custom_ActiveModelDocChangePreNotify != null)
                Custom_ActiveModelDocChangePreNotify(null, new SolidWorksEventArgs(activeDocChangePreNotify_PathOrTitle: a, activeDocChangePreNotify_CreateDate: b));
        }

        void DetachLastDocumentSink()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.CallbackExecution);
            // Modified this method to iterate all open docs and untie all

            try
            {
                // Unsubscribe sink from previously active modeldoc2

                int documentCount = _OwnerAddin.SolidWorksApplication.GetDocumentCount();
                if (documentCount == 0) return;
                EnumDocuments2 documents = _OwnerAddin.SolidWorksApplication.EnumDocuments2();
                documents.Reset();

                ModelDoc2 doc = null;
                int fetched = 0;
                int documentIndex = 0;

                do
                {
                    if (doc != null)
                    {
                        int refCount = Marshal.ReleaseComObject(doc);
                        doc = null;
                    }
                   
                    documents.Next(1, out doc, ref fetched);
                    documentIndex++;

                    if (doc != null && fetched == 1)
                    {
                        //DetachDocumentSink(doc);
                        
                        string a = doc.GetPathName();
                        if (a.Length == 0) a = doc.GetTitle();
                        string b = doc.SummaryInfo[(int)swSummInfoField_e.swSumInfoCreateDate2];

                        if (a.ToUpper() == LastKnownDocumentPath.ToUpper() &&
                            b.ToUpper() == LastKnownDocumentCreate.ToUpper())
                        {
                            Custom_PreNotifyCallback(a, b);

                            //todo: is the above invocation synchronous?
                            // rpc_e complains that below 'doc' has been released
                            // see modelviewhost for exceptions

                            DetachDocumentSink(doc);

                            //ResetLastKnownDocRef();
                            documents.Skip(documentCount - documentIndex);
                         
                        }
                    }

                } while (doc != null);

                ResetLastKnownDocRef();

                GC.Collect();
            }
            catch (Exception ex)
            {
                _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Exception, "detach last document sink : " + ex.Message);
            }
        }

        void ResetLastKnownDocRef()
        {
            LastKnownDocumentPath = NO_DOC;
            LastKnownDocumentCreate = NO_DOC;
        }

        public void DetachCurrentDocumentSink()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.CallbackExecution);
            DetachDocumentSink(_OwnerAddin.SolidWorksApplication.IActiveDoc2);
        }

        void DetachDocumentSink(ModelDoc2 doc)
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.CallbackExecution);
            try
            {
                //System.Windows.Forms.MessageBox.Show("detaching current doc sink 1 @ " +
                //   ((swDocumentTypes_e)(doc).GetType()).ToString());

                switch ((swDocumentTypes_e)(doc).GetType())
                {
                    case swDocumentTypes_e.swDocASSEMBLY:
                        //((AssemblyDoc)doc).FileSaveNotify -= new DAssemblyDocEvents_FileSaveNotifyEventHandler(Document_FileSaveNotify_Callback);
                        //((AssemblyDoc)doc).DestroyNotify2 -= new DAssemblyDocEvents_DestroyNotify2EventHandler(Document_DestroyNotify_Callback);
                        //((AssemblyDoc)doc).NewSelectionNotify -= new DAssemblyDocEvents_NewSelectionNotifyEventHandler(Document_NewSelectionNotify_Callback);
                        break;
                    case swDocumentTypes_e.swDocDRAWING:
                        //((DrawingDoc)doc).FileSaveNotify -= new DDrawingDocEvents_FileSaveNotifyEventHandler(Document_FileSaveNotify_Callback);
                        //((DrawingDoc)doc).DestroyNotify2 -= new DDrawingDocEvents_DestroyNotify2EventHandler(Document_DestroyNotify_Callback);
                        //((DrawingDoc)doc).NewSelectionNotify -= new DDrawingDocEvents_NewSelectionNotifyEventHandler(Document_NewSelectionNotify_Callback);
                        break;
                    case swDocumentTypes_e.swDocNONE:

                        break;
                    case swDocumentTypes_e.swDocPART:

                        if (_OwnerAddin.IsWindowsXP)
                        {
                            ((PartDoc)doc).FileSaveNotify -= new DPartDocEvents_FileSaveNotifyEventHandler(Document_FileSaveNotify_Callback);
                            ((PartDoc)doc).DestroyNotify2 -= new DPartDocEvents_DestroyNotify2EventHandler(Document_DestroyNotify_Callback);
                            ((PartDoc)doc).NewSelectionNotify -= new DPartDocEvents_NewSelectionNotifyEventHandler(Document_NewSelectionNotify_Callback);
                        }
                        else
                        {
                            try
                            {
                                _DocumentSinkPoint.Unadvise(_DocumentSinkCookie);
                                Marshal.ReleaseComObject(_DocumentSinkPoint);
                                _DocumentSinkPoint = null;
                                _DocumentSinkCookie = -1;
                                _DocumentSinkImpl = null;
                                GC.Collect();
                            }
                            catch { }
                        }

                        /*
                         * Win7 issue remains with this strategy
                        EventInfo ei = ReflectionExtension.GetPartDocEvent("NewSelectionNotify");
                        ei.RemoveEventHandler(ReflectionExtension.WrapPartDocClass(doc), PartDocNewSelectionDelegate);
                        */

                        break;
                    case swDocumentTypes_e.swDocSDM:

                        break;
                    default:
                        _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Exception, "switch default: SHOULD NOT BE HERE!");
                        break;
                }

                ResetLastKnownDocRef();
                
            }
            catch (Exception ex) { _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Exception, "detach document sink: " + ex.Message); }
        }

        void AttachCurrentDocumentSink()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.CallbackExecution);
            try
            {
                //System.Windows.Forms.MessageBox.Show("attaching current doc sink 1 @ " +
                //   ((swDocumentTypes_e)(_OwnerAddin.SolidWorksApplication.IActiveDoc2).GetType()).ToString());

                //Mark the active doc for future sink release
                LastKnownDocumentPath = _OwnerAddin.SolidWorksApplication.IActiveDoc2.GetPathName();
                if (LastKnownDocumentPath.Length == 0) LastKnownDocumentPath = _OwnerAddin.SolidWorksApplication.IActiveDoc2.GetTitle();
                LastKnownDocumentCreate = _OwnerAddin.SolidWorksApplication.IActiveDoc2.SummaryInfo[(int)swSummInfoField_e.swSumInfoCreateDate2];

                switch ((swDocumentTypes_e)(_OwnerAddin.SolidWorksApplication.IActiveDoc2).GetType())
                {
                    case swDocumentTypes_e.swDocASSEMBLY:
                        //((AssemblyDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).FileSaveNotify += new DAssemblyDocEvents_FileSaveNotifyEventHandler(Document_FileSaveNotify_Callback);
                        //((AssemblyDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).DestroyNotify2 += new DAssemblyDocEvents_DestroyNotify2EventHandler(Document_DestroyNotify_Callback);
                        //((AssemblyDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).NewSelectionNotify += new DAssemblyDocEvents_NewSelectionNotifyEventHandler(Document_NewSelectionNotify_Callback);
                        break;
                    case swDocumentTypes_e.swDocDRAWING:
                        //((DrawingDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).FileSaveNotify += new DDrawingDocEvents_FileSaveNotifyEventHandler(Document_FileSaveNotify_Callback);
                        //((DrawingDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).DestroyNotify2 += new DDrawingDocEvents_DestroyNotify2EventHandler(Document_DestroyNotify_Callback);
                        //((DrawingDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).NewSelectionNotify += new DDrawingDocEvents_NewSelectionNotifyEventHandler(Document_NewSelectionNotify_Callback);
                        break;
                    case swDocumentTypes_e.swDocNONE:

                        break;
                    case swDocumentTypes_e.swDocPART:
                        _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.UserExecution,"Attaching as PartDoc");

                        if (_OwnerAddin.IsWindowsXP)
                        {
                            // xp only
                            ((PartDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).FileSaveNotify += new DPartDocEvents_FileSaveNotifyEventHandler(Document_FileSaveNotify_Callback);
                            ((PartDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).DestroyNotify2 += new DPartDocEvents_DestroyNotify2EventHandler(Document_DestroyNotify_Callback);
                            ((PartDoc)_OwnerAddin.SolidWorksApplication.IActiveDoc2).NewSelectionNotify += new DPartDocEvents_NewSelectionNotifyEventHandler(Document_NewSelectionNotify_Callback);
                        }
                        else
                        {
                            // Win 7 only
                            IConnectionPointContainer container = _OwnerAddin.SolidWorksApplication.IActiveDoc2 as IConnectionPointContainer;
                            container.FindConnectionPoint(_DocumentSinkGuid, out _DocumentSinkPoint);
                            _DocumentSinkImpl = new DPartDocEventsImpl(_OwnerAddin);
                            _DocumentSinkPoint.Advise(_DocumentSinkImpl, out _DocumentSinkCookie);
                            Marshal.ReleaseComObject(container);
                            container = null;
                        }

                        /*
                         * Win7 issue remains with this strategy
                        EventInfo ei = ReflectionExtension.GetPartDocEvent("NewSelectionNotify");
                        PartDocNewSelectionDelegate = Delegate.CreateDelegate(
                                       ei.EventHandlerType,
                                       this,
                                       this.GetType().GetMethod("Document_NewSelectionNotify_Callback"));
                        ei.AddEventHandler(ReflectionExtension.WrapPartDocClass(_OwnerAddin.SolidWorksApplication.IActiveDoc2), PartDocNewSelectionDelegate);
                        */

                        break;
                    case swDocumentTypes_e.swDocSDM:
                        
                        break;
                    default:
                        _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Exception,"switch default: SHOULD NOT BE HERE!");
                        break;
                }
            }
            catch (Exception ex) { _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Exception, "attach current document sink: " + ex.Message); }
        }

        //private Delegate PartDocNewSelectionDelegate = null;

        public int Document_NewSelectionNotify_Callback()
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(Document_NewSelectionNotify)));
            if (Document_NewSelectionNotify != null) Document_NewSelectionNotify(null, new SolidWorksEventArgs());

            return 0;
        }

        public int Document_DestroyNotify_Callback(int DestroyType)
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(Document_DestroyNotify)));
            if (Document_DestroyNotify != null) Document_DestroyNotify(null, new SolidWorksEventArgs(DestroyType: (swDestroyNotifyType_e)DestroyType));

            // Unsubscribe from the current doc's events when the doc is destroyed.
            DetachCurrentDocumentSink();

            return 0;
        }

        int Document_FileSaveNotify_Callback(string FileName)
        {
            _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Callback, String.Join("", GetInvocationList(Document_FileSaveNotify)));
            if (Document_FileSaveNotify != null) Document_FileSaveNotify(null, new SolidWorksEventArgs(Filename: FileName));

            return 0;
        }
        

        #endregion
    }
}
